package thesis.filesharing.web.controller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import thesis.filesharing.bo.LecturerBO;
import thesis.filesharing.common.exception.functional.InvalidArgumentException;
import thesis.filesharing.common.util.Utils;
import thesis.filesharing.common.util.XMLUtil;
import thesis.filesharing.common.valueobjects.SearchLecturerValueObject;
import thesis.filesharing.model.Lecturer;
import thesis.filesharing.resources.Constants;
import thesis.filesharing.services.fulltextsearch.indexing.DocumentIndexing;
import thesis.filesharing.services.fulltextsearch.indexing.DocumentQuery;
import thesis.filesharing.services.fulltextsearch.indexing.Operators;
import thesis.filesharing.services.fulltextsearch.indexing.QueryBuilder;
import thesis.filesharing.web.auth.AuthUtil;
import thesis.filesharing.web.auth.Roles;
import thesis.filesharing.web.util.PageInfo;
import thesis.filesharing.web.util.PropertiesUtil;
import thesis.filesharing.web.util.Util;
import thesis.filesharing.web.validator.CreateLecturerValidator;
import thesis.filesharing.web.validator.LecturerChangePasswordValidator;
import thesis.filesharing.web.validator.UpdateLecturerInfoValidator;
import thesis.filesharing.web.validator.UpdateLecturerValidator;
import thesis.filesharing.web.viewmodel.ChangePasswordViewModel;
import thesis.filesharing.web.viewmodel.CreateLecturerViewModel;
import thesis.filesharing.web.viewmodel.UpdateLecturerInfoViewModel;
import thesis.filesharing.web.viewmodel.UpdateLecturerViewModel;
import thesis.filesharing.web.viewmodel.ajaxbeans.LecturerBean;

import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.search.Field;
import com.google.appengine.api.search.Results;
import com.google.appengine.api.search.ScoredDocument;

@Controller
public class LecturerController {
	
	@Autowired
	private CreateLecturerValidator createLecturerValidator;
	
	@Autowired
	private UpdateLecturerInfoValidator updateLecturerInfoValidator;
	
	@Autowired
	private UpdateLecturerValidator updateLecturerValidator;
	
	@Autowired
	private LecturerChangePasswordValidator lecturerChangePasswordValidator;
	
	@Autowired
	private LecturerBO lecturerBO;
	
	List<String> departmentNames = XMLUtil.readDataNodes(Constants.DEPARTMENTS_FILE_PATH, "department-name");
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/createLecturer", method = RequestMethod.GET)
	public String initCreateLecturerForm(ModelMap model) {
		CreateLecturerViewModel createLecturerViewModel = new CreateLecturerViewModel();
		model.addAttribute("createLecturerViewModel", createLecturerViewModel);
		return "admin/createLecturer";
	}
	
	@ModelAttribute("departments")
	public Map<Integer, String> populateDepartments() {
		Map<Integer, String> departments = new LinkedHashMap<Integer, String>();
		for (int i = 0; i < departmentNames.size(); i++) {
			departments.put(i, departmentNames.get(i));
		}
		return departments;
	}
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/createLecturer", method = RequestMethod.POST)
	public ModelAndView processCreateLecturerSubmit(
			@ModelAttribute("createLecturerViewModel") CreateLecturerViewModel createLecturerViewModel,
			BindingResult bindingResult, SessionStatus status, ModelMap model, HttpSession session) {
		
		createLecturerValidator.validate(createLecturerViewModel, bindingResult);
		
		if (bindingResult.hasErrors()) {
			return new ModelAndView("admin/createLecturer");
		} else {
			status.setComplete();
			
			boolean isActive = true;
			String department = departmentNames.get(Integer.parseInt(createLecturerViewModel.getDepartment()));
			Set<String> roles = new HashSet<String>();
			roles.add(Roles.ROLE_LECTURER.getRole());
			if (createLecturerViewModel.getIsAdmin()) {
				roles.add(Roles.ROLE_ADMIN.getRole());
			}
			Lecturer lecturer = new Lecturer(createLecturerViewModel.getLecturerId(), createLecturerViewModel.getLecturerName(), 
					createLecturerViewModel.getLecturerEmail(), Util.generatePassword(), department, isActive, roles);
			lecturer = lecturerBO.saveLecturer(lecturer);
			DocumentIndexing.addEntityIndex(lecturer, Constants.SEARCH_INDEX_NAME);
			
			// Use MailService to send a mail to lecturer 's email
			
			session.setAttribute("msg", PropertiesUtil.getProperty("lecturer.create.success"));
			
			return new ModelAndView(new RedirectView("/admin/listLecturers.html"));
		}
	}
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/listLecturers", method = RequestMethod.GET)
	public String listLecturers(@RequestParam(value = "p", required = false, defaultValue = "1") Integer p, ModelMap model) {
		if (p < 1 || p > Util.calculateNumberOfPage(lecturerBO.countLecturers(), Constants.PAGE_SIZE)) {
			p = 1;
		}
		List<Lecturer> lecturers = lecturerBO.listLecturers(p, Constants.PAGE_SIZE);
		model.addAttribute("lecturers", lecturers);
		model.addAttribute("pageInfo", new PageInfo(lecturerBO.countLecturers(), p));
		model.addAttribute("departmentNames", departmentNames);
		model.addAttribute("pageSize", Constants.PAGE_SIZE);
		
		return "admin/listLecturers";
	}
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/deleteLecturer/{key}", method = RequestMethod.GET)
	public RedirectView deleteLecturer(@PathVariable("key") String key, 
			@RequestParam(value = "p", required = false, defaultValue = "1") Integer p,
			ModelMap model, HttpSession session, HttpServletRequest request) {
		if (p < 1 || p > Util.calculateNumberOfPage(lecturerBO.countLecturers(), Constants.PAGE_SIZE)) {
			p = 1;
		}
		
		int totalItems = 0;
		int newPage = 1;
		
		if (request.getParameter("lId") == null) {
			totalItems = lecturerBO.countLecturers();
		} else {
			totalItems = lecturerBO.countFoundLecturers(buildSearchObject(request));
		}
		
		lecturerBO.deleteLecturer(key);
		DocumentIndexing.removeEntityIndex(key, Constants.SEARCH_INDEX_NAME);
		
		session.setAttribute("msg", PropertiesUtil.getProperty("lecturer.delete.success"));
		newPage = Util.evaluatePageNumAfterDelete(p, totalItems, Constants.PAGE_SIZE, 1);
		
		if (request.getParameter("lId") == null) {
			return new RedirectView("/admin/listLecturers.html?p=" + newPage);	
		} else {
			Map<String, String> modifiedParams = new HashMap<String, String>();
			modifiedParams.put("p", String.valueOf(newPage));
			return new RedirectView("/admin/searchLecturers.html?" + Util.modifyQueryString(request, modifiedParams));
		}
	}
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/deleteLecturers", method = RequestMethod.POST)
	public RedirectView deleteLecturers(@RequestParam(value = "p", required = false, defaultValue = "1") Integer p,
			ModelMap model, HttpServletRequest request, HttpSession session) {
		if (p < 1 || p > Util.calculateNumberOfPage(lecturerBO.countLecturers(), Constants.PAGE_SIZE)) {
			p = 1;
		}
		
		String[] lecturerKeys = request.getParameterValues("checkItem");
		int totalItems = 0;
		int newPage = 1;
		
		if (request.getParameter("lId") == null) {
			totalItems = lecturerBO.countLecturers();
		} else {
			totalItems = lecturerBO.countFoundLecturers(buildSearchObject(request));
		}
		
		if (lecturerKeys != null && lecturerKeys.length > 0) {
			for (String k : lecturerKeys) {
				lecturerBO.deleteLecturer(k);
			}
			DocumentIndexing.removeEntityIndexes(new ArrayList<String>(Arrays.asList(lecturerKeys)), Constants.SEARCH_INDEX_NAME);
			newPage = Util.evaluatePageNumAfterDelete(p, totalItems, Constants.PAGE_SIZE, lecturerKeys.length);
		}
		session.setAttribute("msg", PropertiesUtil.getProperty("lecturer.delete.success"));
		
		if (request.getParameter("lId") == null) {
			return new RedirectView("/admin/listLecturers.html?p=" + newPage);
		} else {
			Map<String, String> modifiedParams = new HashMap<String, String>();
			modifiedParams.put("p", String.valueOf(newPage));
			return new RedirectView("/admin/searchLecturers.html?" + Util.modifyQueryString(request, modifiedParams));
		}
	}
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/updateLecturer/{key}", method = RequestMethod.GET)
	public String initUpdateLecturerForm(@PathVariable("key") String key, ModelMap model) {
		UpdateLecturerViewModel updateLecturerViewModel = new UpdateLecturerViewModel();
		Lecturer l = lecturerBO.findLecturerByKey(KeyFactory.stringToKey(key));
		if (l != null) {
			updateLecturerViewModel.setLecturerKey(KeyFactory.keyToString(l.getLecturerKey()));
			updateLecturerViewModel.setLecturerId(l.getLecturerId());
			updateLecturerViewModel.setLecturerName(l.getLecturerName());
			updateLecturerViewModel.setLecturerEmail(l.getLecturerEmail());
			updateLecturerViewModel.setDepartment(String.valueOf(departmentNames.indexOf(l.getLecturerDepartment())));
			updateLecturerViewModel.setIsAdmin(l.getRoles().contains(Roles.ROLE_ADMIN.getRole()));
		}
		model.addAttribute("updateLecturerViewModel", updateLecturerViewModel);
		return "admin/updateLecturer";
	}
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/updateLecturer/{key}", method = RequestMethod.POST)
	public ModelAndView processUpdateLecturerSubmit(
			@ModelAttribute("updateLecturerViewModel") UpdateLecturerViewModel updateLecturerViewModel,
			@RequestParam(value = "p", required = false, defaultValue = "1") Integer p,
			BindingResult bindingResult, SessionStatus status, ModelMap model, HttpSession session, HttpServletRequest request) {
		
		updateLecturerValidator.validate(updateLecturerViewModel, bindingResult);
		
		if (bindingResult.hasErrors()) {
			return new ModelAndView("admin/updateLecturer");
		} else {
			status.setComplete();
			
			boolean isActive = true;
			String department = departmentNames.get(Integer.parseInt(updateLecturerViewModel.getDepartment()));
			Set<String> roles = new HashSet<String>();
			roles.add(Roles.ROLE_LECTURER.getRole());
			if (updateLecturerViewModel.getIsAdmin()) {
				roles.add(Roles.ROLE_ADMIN.getRole());
			}
			
			Lecturer lecturer = lecturerBO.findLecturerByKey(KeyFactory.stringToKey(updateLecturerViewModel.getLecturerKey()));
			
			Lecturer l = new Lecturer(KeyFactory.stringToKey(updateLecturerViewModel.getLecturerKey()), updateLecturerViewModel.getLecturerId(), 
					updateLecturerViewModel.getLecturerName(), updateLecturerViewModel.getLecturerEmail(), lecturer.getLecturerPassword(), department, isActive, roles);
			l = lecturerBO.updateLecturer(l);
			DocumentIndexing.addEntityIndex(l, Constants.SEARCH_INDEX_NAME);
			
			session.setAttribute("msg", PropertiesUtil.getProperty("lecturer.update.success"));
			
			if (request.getParameter("lId") == null) {
				return new ModelAndView(new RedirectView("/admin/listLecturers.html?p=" + p));
			} else {
				String queryString = request.getQueryString();
				return new ModelAndView(new RedirectView("/admin/searchLecturers.html?" + queryString));
			}
		}
	}
	
	@PreAuthorize("hasRole('ROLE_LECTURER')")
	@RequestMapping(value = "/lecturer/changePassword", method = RequestMethod.GET)
	public String initChangePasswordForm(ModelMap model) {
		ChangePasswordViewModel changePasswordViewModel = new ChangePasswordViewModel();
		model.addAttribute("changePasswordViewModel", changePasswordViewModel);
		return "lecturer/changePassword";
	}
	
	@PreAuthorize("hasRole('ROLE_LECTURER')")
	@RequestMapping(value = "/lecturer/changePassword", method = RequestMethod.POST)
	public ModelAndView processChangePasswordSubmit(
			@ModelAttribute("changePasswordViewModel") ChangePasswordViewModel changePasswordViewModel,
			BindingResult bindingResult, SessionStatus status, HttpSession session) {
		
		lecturerChangePasswordValidator.validate(changePasswordViewModel, bindingResult);
		
		if (bindingResult.hasErrors()) {
			return new ModelAndView("lecturer/changePassword");
		} else {
			status.setComplete();
			Lecturer l = lecturerBO.findLecturerById(AuthUtil.getCurrentAuthenticatedUsername());
			if (l != null) {
				Lecturer lecturer = new Lecturer(l.getLecturerKey(), l.getLecturerId(), l.getLecturerName(), l.getLecturerEmail(), 
						changePasswordViewModel.getNewPassword(), l.getLecturerDepartment(), l.isActive(), l.getRoles());
				lecturerBO.updateLecturer(lecturer);
				session.setAttribute("msg", PropertiesUtil.getProperty("lecturer.changePassword.success"));
			} else {
				session.setAttribute("msg", PropertiesUtil.getProperty("lecturer.changePassword.failure"));
			}
			return new ModelAndView(new RedirectView("/lecturer/changePassword.html"));
		}
	}
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/searchLecturers", method = RequestMethod.GET)
	public String searchStudent(@RequestParam(value = "lId", required = false, defaultValue = "") String lecturerId,
			@RequestParam(value = "lName", required = false, defaultValue = "") String lecturerName,
			@RequestParam(value = "lEmail", required = false, defaultValue = "") String lecturerEmail,
			@RequestParam(value = "lDepartment", required = false, defaultValue = "-1") Integer departmentName,
			@RequestParam(value = "p", required = false, defaultValue = "1") Integer p,
			ModelMap model) {
		
		SearchLecturerValueObject searchObj = new SearchLecturerValueObject();
		Map<String, String> params = new HashMap<String, String>();
		
		if (lecturerId != null && !"".equals(lecturerId)) {
			searchObj.setLecturerId(lecturerId);
			params.put("lId", lecturerId);
		}
		
		if (lecturerName != null && !"".equals(lecturerName)) {
			searchObj.setLecturerName(lecturerName);
			params.put("lName", lecturerName);
		}
		
		if (lecturerEmail != null && !"".equals(lecturerEmail)) {
			searchObj.setLecturerEmail(lecturerEmail);
			params.put("lEmail", lecturerEmail);
		}
		
		if (departmentName != null && departmentName >= 0 && departmentName < departmentNames.size()) {
			searchObj.setLecturerDepartment(departmentNames.get(departmentName));
			params.put("lDepartment", String.valueOf(departmentName));
		}
		
		model.addAttribute("lecturers", lecturerBO.searchLecturers(searchObj, p, Constants.PAGE_SIZE));
		model.addAttribute("pageInfo", new PageInfo(lecturerBO.countFoundLecturers(searchObj), p));
		model.addAttribute("pageSize", Constants.PAGE_SIZE);
		model.addAttribute("departmentNames", departmentNames);
		model.addAttribute("params", params);
		
		return "admin/searchLecturers";
	}
	
	private SearchLecturerValueObject buildSearchObject(HttpServletRequest request) {
		String lecturerId = request.getParameter("lId");
		String lecturerName = request.getParameter("lName");
		String lecturerEmail = request.getParameter("lEmail");
		String departmentName = request.getParameter("lDeparment");
		
		SearchLecturerValueObject searchObj = new SearchLecturerValueObject();
		
		try {
			if (lecturerId != null && !"".equals(lecturerId)) {
				searchObj.setLecturerId(lecturerId);
			}
			if (lecturerName != null && !"".equals(lecturerName)) {
				searchObj.setLecturerName(lecturerName);
			}
			if (lecturerEmail != null && !"".equals(lecturerEmail)) {
				searchObj.setLecturerEmail(lecturerEmail);
			}
			if (departmentName != null && !"".equals(departmentName) && Integer.parseInt(departmentName) >= 0 && 
					Integer.parseInt(departmentName) < departmentNames.size()) {
				searchObj.setLecturerDepartment(departmentNames.get(Integer.parseInt(departmentName)));
			}
		} catch (Exception ex) {
			ex.printStackTrace();
			throw new InvalidArgumentException(ex);
		}
		
		return searchObj;
	}
	
	@PreAuthorize("hasRole('ROLE_LECTURER')")
	@RequestMapping(value = "/lecturer/info", method = RequestMethod.GET)
	public String initUpdateLecturerInfoForm(ModelMap model) {
		UpdateLecturerInfoViewModel updateLecturerInfoViewModel = new UpdateLecturerInfoViewModel();
		String currentLecturerId = AuthUtil.getCurrentAuthenticatedUsername();
		if (currentLecturerId != null) {
			Lecturer lecturer = lecturerBO.findLecturerById(currentLecturerId);
			if (lecturer != null) {
				updateLecturerInfoViewModel.setLecturerKey(KeyFactory.keyToString(lecturer.getLecturerKey()));
				updateLecturerInfoViewModel.setLecturerId(lecturer.getLecturerId());
				updateLecturerInfoViewModel.setLecturerName(lecturer.getLecturerName());
				updateLecturerInfoViewModel.setLecturerEmail(lecturer.getLecturerEmail());
				updateLecturerInfoViewModel.setDepartment(String.valueOf(departmentNames.indexOf(lecturer.getLecturerDepartment())));
			}
		}
		model.addAttribute("updateLecturerInfoViewModel", updateLecturerInfoViewModel);
		return "lecturer/info";
	}
	
	@PreAuthorize("hasRole('ROLE_LECTURER')")
	@RequestMapping(value = "/lecturer/info", method = RequestMethod.POST)
	public ModelAndView processUpdateLecturerInfoSubmit(
			@ModelAttribute("updateLecturerInfoViewModel") UpdateLecturerInfoViewModel updateLecturerInfoViewModel,
			@RequestParam(value = "p", required = false, defaultValue = "1") Integer p,
			BindingResult bindingResult, SessionStatus status, ModelMap model, HttpSession session, HttpServletRequest request) {
		
		updateLecturerInfoValidator.validate(updateLecturerInfoViewModel, bindingResult);
		
		if (bindingResult.hasErrors()) {
			return new ModelAndView("lecturer/info");
		} else {
			status.setComplete();
			
			String department = departmentNames.get(Integer.parseInt(updateLecturerInfoViewModel.getDepartment()));
			Lecturer l = lecturerBO.findLecturerByKey(KeyFactory.stringToKey(updateLecturerInfoViewModel.getLecturerKey()));
			Set<String> roles = new HashSet<String>(l.getRoles());
			
			l = lecturerBO.updateLecturer(new Lecturer(l.getLecturerKey(), l.getLecturerId(), updateLecturerInfoViewModel.getLecturerName(), 
					updateLecturerInfoViewModel.getLecturerEmail(), l.getLecturerPassword(), department, l.isActive(), roles));
			DocumentIndexing.addEntityIndex(l, Constants.SEARCH_INDEX_NAME);
			
			session.setAttribute("msg", PropertiesUtil.getProperty("lecturer.update.success"));
			return new ModelAndView(new RedirectView("/lecturer/info.html"));
		}
	}
	
	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/admin/searchLecturerInfoAjax", method = RequestMethod.GET)
	public void searchLecturerInfoAjax(@RequestParam("q") String q, HttpServletResponse response) throws IOException {
		response.setContentType("application/json");
		PrintWriter out = response.getWriter();
		
		q = Utils.insertSignChar(Utils.filterVietnameseSign(q));
		if (q.trim().length() > 0) {
			QueryBuilder builder = new QueryBuilder();
			String query = builder.add("fLecturerId", q, Operators.COLON)
					.or("fLecturerName", q, Operators.COLON)
					.buildQuery();
			
			Results<ScoredDocument> results = DocumentQuery.search(query, Constants.SEARCH_INDEX_NAME);
			List<LecturerBean> lecturerBeans = new ArrayList<LecturerBean>();
			
			for (ScoredDocument result : results) {
				Iterator<Field> fields = result.getFields().iterator();
				
				Field f = null;
				String fieldName = null;
				LecturerBean lecturerBean = new LecturerBean();
				lecturerBean.setLecturerKey(result.getId());
				
				while (fields.hasNext()) {
					f = fields.next();
					fieldName = f.getName();
					
					if ("lecturerId".equals(fieldName)) {
						lecturerBean.setLecturerId(f.getText());
					} else if ("lecturerName".equals(fieldName)) {
						lecturerBean.setLecturerName(f.getText());
					} else if ("lecturerDepartment".equals(fieldName)) {
						lecturerBean.setLecturerDepartment(f.getText());
					}
				}
				
				lecturerBeans.add(lecturerBean);
			}
			
			ObjectMapper mapper = new ObjectMapper();
			out.print(mapper.writeValueAsString(lecturerBeans));
		} else {
			out.print("[]");
		}
	}
}
